<?php
/**
 *
 * @author 凡墙<jihaoju@qq.com>
 * @date 2018/7/13 14:59
 * @description
 */
namespace apps\marriage\core\service;

use apps\marriage\core\logic\FileLogic;
use apps\marriage\core\model\User;
use apps\marriage\core\model\UserAccountBook;
use apps\marriage\core\model\UserPlatform;
use apps\marriage\core\model\UserWithdrawCash;
use think\Exception;
use vm\com\BaseModel;
use vm\com\BaseService;
use vm\org\emoji\Emoji;
use vm\org\EmojiUtil;
use vm\org\ImageUtil;

class UserService extends BaseService
{
    const ACCESS_TOKEN_CACHE_PREFIX = 'access_token:';
    /**
     * @var CacheService
     */
    protected $cacheService = null;

    /**
     * @var UserPlatformService
     */
    protected $userPlatformService = null;

    protected $cachePrefix = 'user:';

    protected function init()
    {
        parent::init();
        $this->cacheService = service('Cache', SERVICE_NAMESPACE);
        $this->userPlatformService = service('UserPlatform', SERVICE_NAMESPACE);
    }

    /**
     * @return BaseService|UserAccountBookService
     */
    private function getUserAccountService()
    {
        return service('UserAccountBook', SERVICE_NAMESPACE);
    }

    /**
     * @return BaseService|UserWithdrawCashService
     */
    private function getUserWithdrawCashService()
    {
        return service('UserWithdrawCash', SERVICE_NAMESPACE);
    }

    /**
     * @return SiteConfigService
     */
    private function getSiteConfigService()
    {
        return  service('SiteConfig', SERVICE_NAMESPACE);
    }

    /**
     * @return BaseService|MemberProfileService
     */
    private function getMemberProfileService()
    {
        return service('MemberProfile', SERVICE_NAMESPACE);
    }

    /**
     * @return FileLogic
     */
    private function getFileLogic()
    {
        return logic('File', LOGIC_NAMESPACE);
    }

    /**
     * @return User|BaseModel
     */
    protected function getModel()
    {
        return new User();
    }

    public function create(array $data)
    {
        if(isset($data['nick']) && !empty($data['nick']) && in_array($data['nick'], ['admin', '管理员'])) {
            throw new Exception($data['nick'] . '不允许被使用');
        }
        if(isset($data['mobile']) && !check_mobile($data['mobile'])) {
            throw new Exception('手机号格式不正确');
        }
        if(isset($data['password'])) {
            $data['password_salt'] = rand_string(6);
            $data['password'] = md5($data['password'] . $data['password_salt']);
        }
        $data['register_time'] = time();

        $nick = EmojiUtil::filter($data['nick']);
        if(empty($nick)) {
            $nick = hidePhoneNumber($data['mobile']);
        }
        $nickname = $nick;
        $i = 1;
        while (true) {
            if(!parent::info(['nick' => $nick])) {
                break;
            }
            $nick = $nickname . $i;
            $i++;
        }
        $data['nick'] = $nick;

        $userId = parent::create($data);
        if($userId) {
//            $site = config('site.');
//            if(isset($data['fromuid'])
//                && $data['fromuid'] > 0
//                && $site['share_obtain_cash_enable']
//                && $this->userRegisterRecordService->count(['mobile' => $data['mobile']]) == 0) {
//                $money = randLimitNumber($site['share_obtain_cash_min_money'] * 10, $site['share_obtain_cash_max_money'] * 10);
//                $money = $money / 10;
//                $this->increaseWithdrawBalance($data['fromuid'], $money, '推荐好友【'.$data['nick'].'】注册');
//            }
        }
        return $userId;
    }

    public function updateByPk(array $data)
    {
        if(isset($data['mobile']) && !check_mobile($data['mobile'])) {
            throw new Exception('手机号格式不正确');
        }
        if(isset($data['nick']) && !empty($data['nick'])) {
            $user = $this->info(['nick' => $data['nick']]);
            if($user && $user['id'] != $data['id']) {
                throw new Exception('该昵称已存在');
            }
        }
        $result = parent::updateByPk($data);
        if($result) {
            cache($this->cachePrefix . $data['id'], null);
        }
        return $result;
    }

    public function getByPk($id)
    {
//        if($data = cache($this->cachePrefix . $id)) {
//            $data['avatar_url'] = $this->getAvatarUrl($data['avatar']);
//            return $data;
//        }
        $data = parent::getByPk($id);
//        cache($this->cachePrefix . $id, $data, 3600*24*7);
        if($data) {
            $data = arrayExcept($data, ['password', 'password_salt']);
            $data['avatar_url'] = $this->getAvatarUrl($data['avatar'], $id);
            $data['uuid'] = $this->getUuid($data['id']);
        }
        return $data;
    }

    public function info($param)
    {
        $data = parent::info($param);
        if($data) {
            $data = arrayExcept($data, ['password', 'password_salt']);
            $data['avatar_url'] = $this->getAvatarUrl($data['avatar'], $data['id']);
            $data['uuid'] = $this->getUuid($data['id']);
        }
        return $data;
    }

    public function deleteByPk($id)
    {
        $mobile = '000' . rand_string(8, 1);
        while (parent::info(['mobile' => $mobile])) {
            $mobile = '000' . rand_string(8, 1);
        }
        $nick = rand_string(10);
        while (parent::info(['nick' => $nick])) {
            $nick = rand_string(10);
        }
        $data = [
            'id' => $id,
            'real_name' => '',
            'nick' => $nick,
            'mobile' => $mobile,
            'fromuid' => 0,
            'is_deleted' => 1
        ];
        parent::updateByPk($data);
        $this->userPlatformService->delete(['user_id' => $id]);
    }

    public function getUuid($userId)
    {
        $authKey = $this->getSiteConfigService()->getValueByCode('auth_key');
        $hash = md5($userId . $authKey);
        return substr($hash, 0, 16) . $userId . substr($hash, 16);
    }

    public function getUserIdByUuid($uuid)
    {
        $sLen = strlen($uuid);
        return substr($uuid, 16, $sLen - 32);
    }

    public function updatePasswordById($id, $password)
    {
        $passwordSalt = rand_string(6);
        $data = [
            'id' => $id,
            'password_salt' => $passwordSalt,
            'password' => md5($password . $passwordSalt)
        ];
        return $this->updateByPk($data);
    }

    public function getByNick($nick)
    {
        $user = $this->info([
            'nick' => $nick
        ]);
        if($user) {
            $this->updateByPk([
                'id' => $user['id'],
                'last_login_time' => time(),
                'last_ip' => getRealClientIp()
            ]);
            $userDto = arrayExcept($user, ['password', 'password_salt', 'email', 'mobile']);
            $userDto['avatar_url'] = $this->getAvatarUrl($userDto['avatar'], $user['id']);
            return $userDto;
        } else {
            return null;
        }
    }

    public function getByMobile($mobile)
    {
        $user = $this->info([
            'mobile' => $mobile
        ]);
        if($user) {
            $this->updateByPk([
                'id' => $user['id'],
                'last_login_time' => time(),
                'last_ip' => getRealClientIp()
            ]);
            $userDto = arrayExcept($user, ['password', 'password_salt', 'email', 'mobile']);
            $userDto['avatar_url'] = $this->getAvatarUrl($userDto['avatar']);
            return $userDto;
        } else {
            return null;
        }
    }

    public function registerByEmail($email, $nick, $password)
    {
        if($this->info(['nick' => $nick])) {
            throw new Exception(lang('nick_exists'));
        }
        if($this->info(['email' => $email])) {
            throw new Exception(lang('email_exists'));
        }
        $mobile = User::VIRTUAL_MOBILE_PHONE_PREFIX . rand_string(8, 1);
        while (parent::info(['mobile' => $mobile])) {
            $mobile = User::VIRTUAL_MOBILE_PHONE_PREFIX . rand_string(8, 1);
        }
        $passwordSalt = rand_string(6, 1);
        $data  = [
            'nick' => $nick,
            'email' => $email,
            'mobile' => $mobile,
            'password' => md5($password . $passwordSalt),
            'password_salt' => $passwordSalt,
            'intro' => '',
            'register_time' => time()
        ];
        $userId = parent::create($data);
        cache($this->mailVerifyCodeCachePrefix . $email, null);
        return $userId;
    }

    public function loginByEmail($email, $password)
    {
        if(!check_email($email)) {
            throw new Exception(lang('email_invalid'));
        }
        $user = parent::info(['email' => $email]);
        if(empty($user) || $user['password'] != md5($password . $user['password_salt'])) {
            throw new Exception(lang('account_invalid'));
        }

        $this->updateByPk([
            'id' => $user['id'],
            'last_login_time' => time(),
            'last_ip' => getRealClientIp()
        ]);
        $userDto = arrayExcept($user, ['password', 'password_salt', 'email', 'mobile']);
        $userDto['avatar_url'] = $this->getAvatarUrl($userDto['avatar'], $user['id']);
        $userDto['access_token'] = $this->genAccessToken($user);
        $userDto['upload_token'] = $this->getUploadToken($user['id']);
        $userDto['uuid'] = $this->getUuid($userDto['id']);
        return $userDto;
    }

    public function registerByWxAuthWithMobile($mobile, $fromuid, array $wxUserInfo)
    {
        $appId = $this->getSiteConfigService()->getValueByCode('weixin_mp_app_id');
        $openid = $wxUserInfo['openid'];
        $unionid = $wxUserInfo['unionid'];
        $headimgurl = $wxUserInfo['headimgurl'];
        $nickname = $wxUserInfo['nickname'];

        $userPlatform = $this->userPlatformService->getByOpenid($openid);
        if(empty($userPlatform)) {
            $userPlatform = [
                'id' => 0,
                'appid' => $appId,
                'user_id' => 0,
                'union_id' => ''
            ];
        }
        $userPlatform['open_id'] = $openid;
        $userPlatform['access_token'] = '';
        $userPlatform['refresh_token'] = '';
        $userPlatform['access_token_expire'] = 0;
        $userPlatform['platform'] = UserPlatform::PLATFORM_WX_MP;
        if($unionid) {
            $userPlatform['union_id'] = $unionid;
        }
        $nowtime = time();
        $user = [
            'avatar' => $headimgurl,
            'last_login_time' => $nowtime
        ];
        if($userPlatform['user_id'] == 0) {
            $registered = false;
            if($unionid) {
                $unionUsers = $this->userPlatformService->getByUnionid($unionid);
                if($unionUsers) {
                    foreach ($unionUsers as $unionUser) {
                        if($unionUser['user_id'] > 0) {
                            $userPlatform['user_id'] = $unionUser['user_id'];
                            $registered = true;
                            break;
                        }
                    }
                }
            }

            if(!$registered) {  //  从未使用微信账号登录过
                if($mobileUser = $this->info(['mobile' => $mobile])) {    // 手机号已注册过账号
                    if($this->userPlatformService->getByUserIdWithAppid($appId, $mobileUser['id'])) { // 手机号已绑定其他微信账号
                        throw new Exception('该手机号已注册');
                    }
                    $userPlatform['user_id'] = $mobileUser['id'];
                } else {    // 手机号未注册过账号
                    $nick = $nickname;
//                    $emoji = new Emoji();
//                    $nick = $emoji->emoji($nick);
//                    $i = 1;
//                    while (true) {
//                        if(!$this->info(['nick' => $nick])) {
//                            break;
//                        }
//                        $nick = $nickname . $i;
//                        $i++;
//                    }
                    $user['nick'] = $nick;
                    $user['real_name'] = '';
                    $user['password'] = rand_string(8);
                    $user['mobile'] = $mobile;
                    $user['last_ip'] = getRealClientIp();
                    $user['intro'] = '';
                    $user['fromuid'] = $fromuid;
                    $matchmakerUids = [];
                    if($fromuid > 0) {
                        $matchmaker = parent::getByPk($fromuid);
                        if($matchmaker['is_matchmaker'] == 1) {
                            $matchmakerUids[] = $fromuid;
                        }
                        if($matchmaker && $matchmaker['fromuid'] > 0) {
                            $matchmakerUids = array_merge($matchmakerUids, explode(',', $matchmaker['matchmaker_uids']));
                        }
                    }
                    $user['matchmaker_uids'] = empty($matchmakerUids) ? '' : implode(',', $matchmakerUids);

                    $user['phone_auth_status'] = 1;
                    $user['id'] = $this->create($user);
                    $userPlatform['user_id'] = $user['id'];

                    $this->getMemberProfileService()->create([
                       'user_id' => $user['id'],
                       'intro' => ''
                    ]);
                }
            }
        } else {
            $user['id'] = $userPlatform['user_id'];
            $this->updateByPk($user);
            $user = $this->getByPk($user['id']);
        }
        $this->userPlatformService->save($userPlatform);
        $user['uuid'] = $this->getUuid($user['id']);
        return $user;
    }

    public function modifyPasswordByEmail($email, $password)
    {
        if(!check_email($email)) {
            throw new Exception(lang('email_invalid'));
        }
        $passwordSalt = rand_string(6, 1);
        return $this->update([
            'password' => md5($password . $passwordSalt),
            'password_salt' => $passwordSalt,
        ], [
            'email' => $email
        ]);
    }

    public function loginById($userId)
    {
        $this->updateByPk([
            'id' => $userId,
            'last_login_time' => time(),
            'last_ip' => getRealClientIp()
        ]);
        $user = $this->getByPk($userId);
        $userDto = arrayExcept($user, ['password', 'password_salt', 'email', 'mobile']);
        $userDto['access_token'] = $this->genAccessToken($user);
        $userDto['upload_token'] = $this->getUploadToken($user['id']);
        return $userDto;
    }

    public function loginByOpenid($openid)
    {
        $userPlatform = $this->userPlatformService->getByOpenid($openid);
        if($userPlatform == null || $userPlatform['user_id'] == 0) {
            throw new Exception("用户不存在");
        }
        $this->updateByPk([
            'id' => $userPlatform['user_id'],
            'last_login_time' => time(),
            'last_ip' => getRealClientIp()
        ]);
        $user = $this->getByPk($userPlatform['user_id']);
        $userDto = arrayExcept($user, ['password', 'password_salt', 'mobile']);
        $userDto['access_token'] = $this->genAccessToken($user);
        $userDto['upload_token'] = $this->getUploadToken($user['id']);
        $userDto['uuid'] = $this->getUuid($userDto['id']);
        return $userDto;
    }

    public function logout($accessToken)
    {
        $this->cacheService->rm(self::ACCESS_TOKEN_CACHE_PREFIX . $accessToken);
    }

    public function getAuth($accessToken)
    {
        $cacheName = self::ACCESS_TOKEN_CACHE_PREFIX . $accessToken;
        $userId = $this->cacheService->get($cacheName);
        if(empty($userId)) {
            return null;
        }
        return $this->getByPk($userId);
    }

    public function genAccessToken(array $user, $extra = '')
    {
        $lastTokenCacheKey = 'user_access_token:' . $user['id'];
        $lastToken = $this->cacheService->get($lastTokenCacheKey);
        if(!empty($lastToken)) {
            $this->cacheService->rm(self::ACCESS_TOKEN_CACHE_PREFIX . $lastToken);
        }

        $accessToken = md5(uniqid() . $user['id'] . $extra . rand_string(30) . microtime());
        $cacheName = self::ACCESS_TOKEN_CACHE_PREFIX . $accessToken;
        $this->cacheService->set($cacheName, $user['id'], 0);
        $this->cacheService->set($lastTokenCacheKey, $accessToken, 0);
        return $accessToken;
    }

    public function getUploadToken($userId)
    {
        return $this->getFileLogic()->genToken('marriage_user_' . $userId);
    }

    public function getProfileBanner($profileBanner)
    {
        if(is_numeric($profileBanner)) {
            $file = $this->getFileLogic()->file($profileBanner);
            return $file['url'];
        } else {
            return $profileBanner;
        }
    }

    public function getMobilePhone($userId)
    {
        $user = parent::getByPk($userId);
        return $user ? $user['mobile'] : null;
    }

    public function getAvatarUrl($avatar, $userId)
    {
        $avatarUrl = '';
        if(is_numeric($avatar)) {
            $file = $this->getFileLogic()->file($avatar);
            if($file) {
                $avatarUrl = $file['url'];
            }
        } else {
            if(stripos($avatar, 'http') !== false) {
                $hash = md5($avatar);
                $dir = WWW_PATH . 'uploads/avatar/' . $hash{0} . '/' . $hash{1} . '/' . $hash{2} . '/';
                if(!is_dir($dir)) {
                    makeDir($dir);
                }
                $ext = ImageUtil::getExt($avatar);
                $ext = $ext ? $ext : '.jpg';
                $filename = $hash . $ext;
                $localPic = $dir . $filename;
                $filename = ImageUtil::grabImage($avatar, $dir, $filename);
                if(is_file($localPic)) {
                    $file = $this->getFileLogic()->upload('data:image/'.trim($ext, '.').';base64,' . file_get_contents($localPic));
                    $avatarUrl = $file['url'];
                    $this->updateByPk([
                       'id' => $userId,
                        'avatar' => $file['file_id']
                    ]);
                    @unlink($localPic);
                }
            } else {
                $avatarUrl = $avatar;
            }
        }
        return $avatarUrl;
    }

    public function increaseBalance($id, $amount, $record = true, $remark = '', $iidType = null, $iid = null, $extend = null)
    {
        if($this->getModel()->increaseBalance($id, $amount)) {
            if($record) {
                return $this->getUserAccountService()->record($id, UserAccountBook::OP_INCREASE, $amount, UserAccountBook::MONEY_TYPE_MONEY, $iidType, $iid, $extend, $remark);
            }
            return true;
        } else {
            throw new Exception('操作失败');
        }
    }

    public function decreaseBalance($id, $amount, $record = true, $remark = '', $iidType = null, $iid = null, $extend = null)
    {
        if($this->getModel()->decreaseBalance($id, $amount)) {
            if($record) {
                return $this->getUserAccountService()->record($id, UserAccountBook::OP_DECREASE, $amount, UserAccountBook::MONEY_TYPE_MONEY, $iidType, $iid, $extend, $remark);
            }
            return true;
        } else {
            throw new Exception('操作失败');
        }
    }

    public function increaseWithdrawBalance($id, $amount, $record = true, $remark = '', $iidType = null, $iid = null, $extend = null)
    {
        if($this->getModel()->increaseWithdrawBalance($id, $amount)) {
            if($record) {
                return $this->getUserAccountService()->record($id, UserAccountBook::OP_INCREASE, $amount, UserAccountBook::MONEY_TYPE_MONEY, $iidType, $iid, $extend, $remark);
            }
            return true;
        } else {
            throw new Exception('操作失败');
        }
    }

    public function decreaseWithdrawBalance($id, $amount, $record = true, $remark = '', $iidType = null, $iid = null, $extend = null)
    {
        if($this->getModel()->decreaseWithdrawBalance($id, $amount)) {
            if($record) {
                return $this->getUserAccountService()->record($id, UserAccountBook::OP_DECREASE, $amount, UserAccountBook::MONEY_TYPE_MONEY, $iidType, $iid, $extend, $remark);
            }
            return true;
        } else {
            throw new Exception('操作失败');
        }
    }

    /**
     * 申请提现
     * @param int $id               --用户ID
     * @param float $amount         --金额
     * @param int $applyId          --提现申请记录ID
     * @return bool
     * @throws Exception
     */
    public function applyWithdraw($id, $amount, $applyId)
    {
        if(!is_numeric($amount) || $amount <= 0) {
            throw new Exception('参数错误');
        }
        if($this->getModel()->applyWithdraw($id, $amount)) {
            $iidType = UserAccountBook::IID_TYPE_WITHDRAW_CASH;
            $result = $this->getUserAccountService()->record($id, UserAccountBook::OP_DECREASE, $amount, UserAccountBook::MONEY_TYPE_MONEY, $iidType, $applyId, null, '提现');
            return true;
        }
        throw new Exception('申请失败');
    }

    /**
     * 拒绝提现申请
     * @param int $id               --用户ID
     * @param float $amount         --金额
     * @param int $applyId          --提现申请记录ID
     * @return bool
     * @throws Exception
     */
    public function rejectWithdraw($id, $amount, $applyId)
    {
        if($this->getModel()->rejectWithdraw($id, $amount)) {
            $this->getUserAccountService()->record($id, UserAccountBook::OP_INCREASE, $amount, UserAccountBook::MONEY_TYPE_MONEY, UserAccountBook::IID_TYPE_WITHDRAW_CASH, $applyId, null, '提现退回');
            return true;
        } else {
            throw new Exception('操作失败');
        }
    }

    public function finishWithdraw($id, $amount)
    {
        if($this->getModel()->finishWithdraw($id, $amount)) {
            return true;
        } else {
            throw new Exception('操作失败');
        }
    }

    public function increaseGoldCoin($id, $amount, $record = true, $remark = '', $iidType = null, $iid = null, $extend = null)
    {
        if($this->getModel()->increase('gold_coin', ['id' => $id], $amount)) {
            if($record) {
                return $this->getUserAccountService()->record($id, UserAccountBook::OP_INCREASE, $amount, UserAccountBook::MONEY_TYPE_GOLD_COIN, $iidType, $iid, $extend, $remark);
            }
            return true;
        } else {
            throw new Exception('操作失败');
        }
    }

    public function decreaseGoldCoin($id, $amount, $record = true, $remark = '', $iidType = null, $iid = null, $extend = null)
    {
        if($this->getModel()->decrease('gold_coin', ['id' => $id, 'gold_coin' => ['>=', $amount]], $amount)) {
            if($record) {
                return $this->getUserAccountService()->record($id, UserAccountBook::OP_DECREASE, $amount, UserAccountBook::MONEY_TYPE_GOLD_COIN, $iidType, $iid, $extend, $remark);
            }
            return true;
        } else {
            throw new Exception('扣除金币失败');
        }
    }

    public function count(array $params = [])
    {
        $model = $this->getModel();
        foreach ($params as $key => $val) {
            if($key == 'register_begin_time') {
                $model = $model->where('register_time', '>=', $val);
            } else if($key == 'register_end_time') {
                $model = $model->where('register_time', '<=', $val);
            } else if($key == 'login_begin_time') {
                $model = $model->where('last_login_time', '>=', $val);
            } else if($key == 'login_end_time') {
                $model = $model->where('last_login_time', '<=', $val);
            } else if($key == 'matchmaker_uid') {
                $model = $model->whereRaw("CONCAT(',', matchmaker_uids, ',') LIKE '%,$val,%'");
            } else {
                $model = $model->where($key, $val);
            }
        }
        return $model->count();
    }

    /**
     * 申请成为红娘
     * @param int $userId
     * @return bool
     * @throws Exception
     */
    public function applyMatchmaker($userId)
    {
        $member = $this->getMemberProfileService()->getByUserId($userId);
        if($member['realname_auth_status'] == 0) {
            throw new Exception('请先完成实名认证');
        }
        return $this->updateByPk([
            'id' => $userId,
            'is_matchmaker' => 1
        ]);
    }

    /**
     * 撤销红娘资格
     * @param int $userId
     * @return bool
     * @throws Exception
     */
    public function onTerminateMatchmaker($userId)
    {
        return $this->updateByPk([
            'id' => $userId,
            'is_matchmaker' => 0
        ]);
    }
}